package cn.itcast.es.demo;


import cn.itcast.es.pojo.User;
import cn.itcast.es.service.UserService;
import com.alibaba.fastjson.JSON;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpHost;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.CreateIndexResponse;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.util.List;
import java.util.Map;

public class ElasticDemo {

    private RestHighLevelClient client;

    private UserService userService = new UserService();

    /**
     * 建立连接
     */
    @Before
    public void init() throws IOException {
        client = new RestHighLevelClient(
                RestClient.builder(
                        new HttpHost("localhost", 9200, "http")
                )
        );
    }

    /**
     * 关闭客户端连接
     */
    @After
    public void close() throws IOException {
        client.close();
    }

    /**
     * 创建索引库，并设置配置信息和映射关系
     * @throws IOException
     */
    @Test
    public void testCreateIndex() throws IOException {

        // 1.创建CreateIndexRequest对象，并指定索引库名称
        CreateIndexRequest createIndexRequest = new CreateIndexRequest("user");

        // 2.指定settings配置
        createIndexRequest.settings(Settings.builder()
                // 设置有三个分片
                .put("index.number_of_shards",3)
                // 设置有一个副本，_source一份，总共有2份
                .put("index.number_of_replicas",1));

        // 3.指定mapping配置 这段映射可以直接将上面的properties后面的内容直接拷贝下来，直接就会变成json格式的数据
        createIndexRequest.mapping("{\n" +
                "    \"properties\": {\n" +
                "      \"id\": {\n" +
                "        \"type\": \"long\"\n" +
                "      },\n" +
                "      \"name\":{\n" +
                "        \"type\": \"keyword\"\n" +
                "      },\n" +
                "      \"age\":{\n" +
                "        \"type\": \"integer\"\n" +
                "      },\n" +
                "      \"gender\":{\n" +
                "        \"type\": \"keyword\"\n" +
                "      },\n" +
                "      \"note\":{\n" +
                "        \"type\": \"text\",\n" +
                "        \"analyzer\": \"ik_max_word\"\n" +
                "      }\n" +
                "    }\n" +
                "}", XContentType.JSON);

        // 4.发起请求，得到响应
        CreateIndexResponse createIndexResponse = client.indices().create(createIndexRequest, RequestOptions.DEFAULT);

        System.out.println(createIndexResponse.isAcknowledged()); // true

        System.out.println(createIndexResponse.isShardsAcknowledged()); // true

    }

    /**
     * 从数据库中查询数据 添加文档信息
     * @throws IOException
     */
    @Test
    public void addDocument() throws IOException {

        // 1.从数据库查询文档数据
        User user = userService.findById(6L);

        // 2.创建IndexRequest对象，并指定索引库名称
        IndexRequest indexRequest = new IndexRequest("user");

        // 3.指定新增的数据的id，这里的id一定要用string
        indexRequest.id(String.valueOf(user.getId()));

        // 4.将新增的文档数据变成JSON格式
        String jsonString = JSON.toJSONString(user);

        // 5.将JSON数据添加到IndexRequest中
        indexRequest.source(jsonString,XContentType.JSON);

        // 6.发起请求，得到结果
        IndexResponse response = client.index(indexRequest, RequestOptions.DEFAULT);

        System.out.println(response.status());

        System.out.println(response.getResult());

    }

    /**
     * 根据id获取文档信息
     * @throws IOException
     */
    @Test
    public void getDocument() throws IOException {

        // 1.创建GetRequest对象，并指定索引库名称、文档ID
        GetRequest request = new GetRequest("user", "6");

        // 2.发起请求，得到结果
        GetResponse response = client.get(request, RequestOptions.DEFAULT);

        // 3.从结果中得到source，是json字符串
        String source = response.getSourceAsString();

        // 4.将JSON反序列化为对象
        User user = JSON.parseObject(source, User.class);

        System.out.println(user);

    }

    /**
     * 根据id修改文档信息
     * @throws IOException
     */
    @Test
    public void updateDocument() throws IOException {

        // 1.创建UpdateRequest对象，指定索引库名称、文档ID
        UpdateRequest updateRequest = new UpdateRequest("user","6");

        // 2.指定要修改的字段及属性值
        updateRequest.doc("gender","男");

        // 3.发起请求
        UpdateResponse updateResponse = client.update(updateRequest, RequestOptions.DEFAULT);

        System.out.println(updateResponse.getResult());

    }

    /**
     * 根据id删除文档信息
     * @throws IOException
     */
    @Test
    public void deleteDocument() throws IOException {

        // 1.创建DeleteRequest对象，指定索引库名称、文档ID
        DeleteRequest deleteRequest = new DeleteRequest("user","6");

        // 2.发起请求
        DeleteResponse deleteResponse = client.delete(deleteRequest, RequestOptions.DEFAULT);

        System.out.println(deleteResponse.getResult());

    }

    /**
     * 使用批量录入文档数据信息
     * @throws IOException
     */
    @Test
    public void testAddBulk() throws IOException {

        // 1.从数据库查询文档数据
        List<User> users = userService.findAll();

        // 2.创建BulkRequest对象
        BulkRequest bulkRequest = new BulkRequest();

        // 3.创建多个IndexRequest对象，并添加到BulkRequest中
        for (User user : users) {
            bulkRequest.add(new IndexRequest("user")
                    .id(String.valueOf(user.getId()))
                    .source(JSON.toJSONString(user),XContentType.JSON)
            );
        }

        // 4.发起请求
        BulkResponse bulkResponse = client.bulk(bulkRequest, RequestOptions.DEFAULT);

        System.out.println(bulkResponse.status());

    }

    /**
     * 查询所有文档信息 没有条件
     * @throws IOException
     */
    @Test
    public void testBasicSearch() throws IOException {

        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

        sourceBuilder.query(QueryBuilders.matchAllQuery());

        SearchRequest searchRequest = new SearchRequest("user");

        searchRequest.source(sourceBuilder);

        SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);

        SearchHits hitsOut = searchResponse.getHits();

        long total = hitsOut.getTotalHits().value;

        System.out.println("total = " + total);

        SearchHit[] hits = hitsOut.getHits();

        for (SearchHit hit : hits) {

            String source = hit.getSourceAsString();

            User user = JSON.parseObject(source, User.class);

            System.out.println(user);

        }

    }

    /**
     * 分词查询
     * @throws IOException
     */
    @Test
    public void testBasicByWordSearch() throws IOException {

        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

        sourceBuilder.query(QueryBuilders.matchQuery("note","唱歌表演"));

        SearchRequest searchRequest = new SearchRequest("user");

        searchRequest.source(sourceBuilder);

        SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);

        SearchHits hitsOut = searchResponse.getHits();

        // 命中个数
        long total = hitsOut.getTotalHits().value;

        System.out.println("total = " + total);

        // 实际查询到的元素集合
        SearchHit[] hits = hitsOut.getHits();

        for (SearchHit hit : hits) {

            String source = hit.getSourceAsString();

            User user = JSON.parseObject(source, User.class);

            System.out.println(user);

        }

    }

    /**
     * 使用bool查询将分词查询和范围查询的查询条件组合起来
     * 这种是不会改变得分的过滤查询
     * @throws IOException
     */
    @Test
    public void testBasicByBoolSearch() throws IOException {

        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();

        queryBuilder.must(QueryBuilders.matchQuery("note","唱歌表演"));

        queryBuilder.filter(QueryBuilders.rangeQuery("age").gte(18).lte(24));

        sourceBuilder.query(queryBuilder);

        SearchRequest searchRequest = new SearchRequest("user");

        searchRequest.source(sourceBuilder);

        SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);

        SearchHits hitsOut = searchResponse.getHits();

        long total = hitsOut.getTotalHits().value;

        System.out.println("total = " + total);

        SearchHit[] hits = hitsOut.getHits();

        for (SearchHit hit : hits) {

            User user = JSON.parseObject(hit.getSourceAsString(), User.class);

            System.out.println(user);

        }

    }

    /**
     * 分词查询 字段过滤查询
     * @throws IOException
     */
    @Test
    public void testBasicByExcludesSearch() throws IOException {

        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

        sourceBuilder.query(QueryBuilders.matchQuery("note","唱歌表演"));

        sourceBuilder.fetchSource(new String[]{"id","age","note"},new String[0]);

        // SearchSourceBuilder的 fetchSource(String[] includes, String[] excludes)
//        sourceBuilder.fetchSource(new String[]{"id","age","note"},new String[]{});
        // new String[]{} 和 new String[0] 是需要排除的字段，这里只需要定义一个空的字符串数组就行

        SearchRequest searchRequest = new SearchRequest("user");

        searchRequest.source(sourceBuilder);

        SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);

        SearchHits hitsOut = searchResponse.getHits();

        long total = hitsOut.getTotalHits().value;

        System.out.println("total = " + total);

        SearchHit[] hits = hitsOut.getHits();

        for (SearchHit hit : hits) {

            User user = JSON.parseObject(hit.getSourceAsString(), User.class);

            System.out.println(user);

        }

    }

    /**
     * 排序 字段过滤
     * @throws IOException
     */
    @Test
    public void testBasicByOrderSearch() throws IOException {

        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

        sourceBuilder.sort("age", SortOrder.DESC);

//        sourceBuilder.fetchSource(new String[]{"id","name","age"},new String[0]);

        SearchRequest searchRequest = new SearchRequest("user");

        searchRequest.source(sourceBuilder);

        SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);

        SearchHits hitsOut = searchResponse.getHits();

        long total = hitsOut.getTotalHits().value;

        System.out.println("total = " + total);

        SearchHit[] hits = hitsOut.getHits();

        for (SearchHit hit : hits) {

            User user = JSON.parseObject(hit.getSourceAsString(), User.class);

            System.out.println(user);

        }

    }

    /**
     * 查询所有并且根据id升序排序 显示首页5条
     * @throws IOException
     */
    @Test
    public void testBasicSearchWithSortAndPage() throws IOException {

        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

//        sourceBuilder.query(QueryBuilders.matchAllQuery());

        sourceBuilder.sort("id",SortOrder.ASC);

        int pageNum = 1, pageSize = 5;

        int from = ( pageNum - 1 ) * pageSize;

        sourceBuilder.from(from);

        sourceBuilder.size(pageSize);

        SearchRequest searchRequest = new SearchRequest("user");

        searchRequest.source(sourceBuilder);

        SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);

        SearchHits hitsOut = searchResponse.getHits();

        long total = hitsOut.getTotalHits().value;

        System.out.println( "total = " + total);

        SearchHit[] hits = hitsOut.getHits();

        for (SearchHit hit : hits) {

            User user = JSON.parseObject(hit.getSourceAsString(), User.class);

            System.out.println(user);

        }

    }

    /**
     * 使用分词查询再对分词的字段进行高亮显示
     * @throws IOException
     */
    @Test
    public void testHighlight() throws IOException {

        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

        sourceBuilder.query(QueryBuilders.matchQuery("note","唱歌表演"));

        sourceBuilder.highlighter(new HighlightBuilder().field("note"));

        SearchRequest searchRequest = new SearchRequest("user");

        searchRequest.source(sourceBuilder);

        SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);

        SearchHits hitsOut = searchResponse.getHits();

        long total = hitsOut.getTotalHits().value;

        System.out.println( "total = " + total);

        SearchHit[] hits = hitsOut.getHits();

        for (SearchHit hit : hits) {

            User user = JSON.parseObject(hit.getSourceAsString(), User.class);

            // 在查询出的字段中会有 highLightField这个json格式的数据
            // 高亮的数据会在这里展示，光获取source里面的数据是没有高亮的
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();

            // 根据json的key值获取到高亮的字符串
            HighlightField HighlightField = highlightFields.get("note");

            // es又对这个这个对象进行了封装 fragments
            Text[] fragments = HighlightField.getFragments();

            String note = StringUtils.join(fragments);

            // 将 note 查询到的高亮字符串通过set方法设置到user对象中去
            user.setNote(note);

            System.out.println(user);

        }

    }

    /**
     * 聚合查询
     * @throws IOException
     */
    @Test
    public void testBasicSearchWithAggregation() throws IOException {

        // 条件构建工具
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

        // 不展示查询结果
        sourceBuilder.size(0);

        // 设置聚合名称以及聚合字段  参与聚合的字段，必须不能分词
        sourceBuilder.aggregation(AggregationBuilders.terms("gender_agg").field("gender"));

        SearchRequest searchRequest = new SearchRequest("user");

        searchRequest.source(sourceBuilder);

        SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);

        // 默认返回的接口类型是 Aggregation，但是该接口中没有 getBuckets这个方法
        // 因此我们需要使用它的子接口 Terms
        Aggregations aggregations = searchResponse.getAggregations();

        Terms terms = aggregations.get("gender_agg");

        List<? extends Terms.Bucket> buckets = terms.getBuckets();

        for (Terms.Bucket bucket : buckets) {

            String key = bucket.getKeyAsString();


            // 获取 Number 类型的数据只有这个方法，设计api时的规范性，下面会有转换成对应的数据类型.intValue()...
//            long value = bucket.getKeyAsNumber().longValue();

            System.out.println( "key = " + key);

            long docCount = bucket.getDocCount();

            System.out.println("docCount = " + docCount);

        }

    }

}
